# main_gui.py
# -*- coding: utf-8 -*-
"""
人脸识别打卡系统 —— 零警告完整 GUI
登录界面不变，所有功能完成后回到上一级
"""
import os, sys, json, subprocess
from datetime import datetime, timedelta
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

from util import camera
from service.hr_service import (
    valid_user, add_new_employee, remove_employee, check_id,
    add_lock_record, get_employee_report, get_record_all, save_work_time
)


# -------------------- 统一工作目录 & 自动创建数据文件 --------------------
os.chdir(os.path.dirname(os.path.abspath(__file__)))
for fname in ["emp.json", "record.json"]:
    if not os.path.exists(fname):
        with open(fname, "w", encoding="utf-8") as f:
            json.dump([] if "record" in fname else {"mr": "mrsoft"}, f)

# 只导入真正用到的函数
from service.hr_service import (valid_user, add_new_employee, remove_employee, check_id,
                                add_lock_record, get_employee_report, get_record_all,
                                get_today_report, get_day_report, save_work_time)

# 兜底工具函数
def valid_date(d):
    try:
        datetime.strptime(d, "%Y-%m-%d")
        return True
    except ValueError:
        return False

def valid_time(t):
    try:
        datetime.strptime(t, "%H:%M:%S")
        return True
    except ValueError:
        return False

def valid_year_month(m):
    try:
        datetime.strptime(m, "%Y-%m")
        return True
    except ValueError:
        return False


# 月报 CSV 兜底
try:
    from service.hr_service import get_month_report_csv
except ImportError:
    def get_month_report_csv(month):
        """把 record.json 中该月的记录导出为 CSV 字符串"""
        try:
            with open("record.json", "r", encoding="utf-8") as f:
                records = json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            records = []

        rows = ["日期,姓名,打卡时间"]
        for rec in records:
            if rec.get("date", "").startswith(month):
                rows.append(f"{rec['date']},{rec['name']},{rec['time']}")

        return "\n".join(rows) + "\n"


# 在 main_gui.py 顶部或类外增加
import cv2, os, numpy as np
from PIL import Image

def _load_recognizer():
    recognizer = cv2.face.LBPHFaceRecognizer_create()
    yml = r"E:\openCV\lock\trainer\trainer.yml"
    recognizer.read(yml)
    return recognizer

def _detect_face(gray):
    cascade = cv2.CascadeClassifier(r"E:\openCV\lock\cascades\haarcascade_frontalface_default.xml")
    faces = cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)
    return faces[0] if len(faces) == 1 else None   # 只取第一张脸

# -------------------- 全局 --------------------
ADMIN_LOGIN = False

# -------------------- 波纹 --------------------
class RippleOverlay(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAttribute(Qt.WA_TransparentForMouseEvents, True)
        self.ripples = []
        self.setFixedSize(parent.size())
        parent.installEventFilter(self)
        self._timer = QTimer(self)
        self._timer.setInterval(16)
        self._timer.timeout.connect(self._animate)

    def add_ripple(self, pos):
        max_r = max(self.width(), self.height()) * 0.7
        step = max_r / 30
        self.ripples.append({'pos': pos, 'r': 0, 'max': max_r, 'step': step})
        self._timer.start()

    def _animate(self):
        for r in self.ripples[:]:
            r['r'] += r['step']
            if r['r'] >= r['max']:
                self.ripples.remove(r)
        self.update() if self.ripples else self._timer.stop()

    def paintEvent(self, event):
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing)
        for r in self.ripples:
            alpha = max(0, 255 * (1 - r['r'] / r['max']))
            grad = QRadialGradient(r['pos'], r['r'])
            grad.setColorAt(0, QColor(0, 255, 255, alpha))
            grad.setColorAt(1, QColor(0, 255, 255, 0))
            p.setBrush(grad)
            p.setPen(Qt.NoPen)
            p.drawEllipse(r['pos'], r['r'], r['r'])

    def eventFilter(self, obj, event):
        if event.type() == event.MouseButtonPress:
            if not self.childAt(event.pos()):
                return True
            self.add_ripple(event.pos())
        return super().eventFilter(obj, event)


# -------------------- 通用基类 --------------------
class CenteredWidget(QWidget):
    closed = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Window)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setFixedSize(1000, 700)
        self.center()

        self.bg = QPixmap(r"E:\openCV\lock\挚友.png")
        if self.bg.isNull():
            self.bg = QPixmap(1000, 700)
            self.bg.fill(Qt.black)
        self.ripple = RippleOverlay(self)

        self.anim_in = QPropertyAnimation(self, b"windowOpacity")
        self.anim_in.setDuration(300)
        self.anim_in.setStartValue(0)
        self.anim_in.setEndValue(1)
        self.anim_in.setEasingCurve(QEasingCurve.OutCubic)

        self.anim_out = QPropertyAnimation(self, b"windowOpacity")
        self.anim_out.setDuration(300)
        self.anim_out.setStartValue(1)
        self.anim_out.setEndValue(0)
        self.anim_out.finished.connect(self.close)
        self.anim_out.finished.connect(self.closed.emit)

    def center(self):
        ag = QDesktopWidget().availableGeometry()
        self.move((ag.width() - self.width()) // 2,
                  (ag.height() - self.height()) // 2)

    def show_with_animation(self):
        super().show()
        self.anim_in.start()

    def close_with_animation(self):
        self.anim_out.start()

    def paintEvent(self, event):
        p = QPainter(self)
        p.drawPixmap(self.rect(), self.bg)
        p.fillRect(self.rect(), QColor(20, 20, 20, 180))

# -------------------- 样式 --------------------
BTN_STYLE = """
QPushButton{
    background-color: rgba(40,40,40,200);
    color: #00FFFF;
    border: 2px solid #00FFFF;
    border-radius: 10px;
    font-size: 20px;
    font-family: "Microsoft YaHei";
    min-width: 300px;
    min-height: 60px;
}
QPushButton:hover{background-color: rgba(0,255,255,60);}
QPushButton:pressed{background-color: rgba(0,255,255,100);}
"""

# -------------------- 通用功能窗口 --------------------
class FuncWindow(CenteredWidget):
    def __init__(self, title, content, auto_close=3):
        super().__init__()
        self.setWindowTitle(title)
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)
        self.label = QLabel(content)
        self.label.setFont(QFont("Microsoft YaHei", 20))
        self.label.setStyleSheet("color:#00FFFF;")
        vbox.addWidget(self.label)
        if auto_close > 0:
            QTimer.singleShot(auto_close * 1000, self.close_with_animation)

# -------------------- 日报详情窗口 --------------------
class ReportDetailWindow(CenteredWidget):
    def __init__(self, title, content):
        super().__init__()
        self.setWindowTitle(title)
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignTop)
        self.text = QTextEdit()
        self.text.setReadOnly(True)
        self.text.setFont(QFont("Consolas", 12))
        self.text.setStyleSheet("color:#00FFFF; background:transparent; border:0;")
        self.text.setPlainText(content)
        vbox.addWidget(self.text)

        hbox = QHBoxLayout()
        back = QPushButton("返回")
        back.setStyleSheet(BTN_STYLE)
        back.clicked.connect(self.close_with_animation)
        hbox.addWidget(back)
        vbox.addLayout(hbox)

# -------------------- 输入窗口 --------------------
class InputWindow(CenteredWidget):
    ok = pyqtSignal(str)

    def __init__(self, title, prompt, placeholder=""):
        super().__init__()
        self.setWindowTitle(title)
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)
        vbox.addWidget(QLabel(prompt))
        self.edit = QLineEdit()
        self.edit.setFont(QFont("Microsoft YaHei", 18))
        self.edit.setPlaceholderText(placeholder)
        vbox.addWidget(self.edit)
        ok = QPushButton("Enter 完成")
        ok.setStyleSheet(BTN_STYLE)
        ok.clicked.connect(lambda: self.ok.emit(self.edit.text()))
        ok.clicked.connect(self.close_with_animation)
        vbox.addWidget(ok)
        self.edit.returnPressed.connect(ok.click)

# -------------------- 登录（不变） --------------------
class LoginWindow(CenteredWidget):
    login_success = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.setWindowTitle("管理员登录")
        self.init_ui()

    def init_ui(self):
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)
        font = QFont("Microsoft YaHei", 16)
        self.user_edit = QLineEdit()
        self.user_edit.setPlaceholderText("输入管理员账号(按0取消)")
        self.user_edit.setFont(font)
        self.user_edit.setFixedWidth(300)
        self.pwd_edit = QLineEdit()
        self.pwd_edit.setPlaceholderText("输入管理员密码")
        self.pwd_edit.setFont(font)
        self.pwd_edit.setEchoMode(QLineEdit.Password)
        self.pwd_edit.setFixedWidth(300)
        ok = QPushButton("登录")
        ok.setStyleSheet(BTN_STYLE)
        ok.clicked.connect(self.do_login)
        vbox.addWidget(self.user_edit)
        vbox.addWidget(self.pwd_edit)
        vbox.addWidget(ok)

    def do_login(self):
        username = self.user_edit.text().strip()
        password = self.pwd_edit.text().strip()
        if username == '0':
            self.close_with_animation()
            return

        try:
            with open("login.json", encoding="utf-8") as f:
                login_map = json.load(f)
        except:
            login_map = {}

        if login_map.get(username) == password:
            global ADMIN_LOGIN
            ADMIN_LOGIN = True
            self.login_success.emit()
            self.close_with_animation()
        else:
            QMessageBox.warning(self, "警告", "账号或密码错误，重新输入")

# -------------------- 主菜单 --------------------
class MainMenuWindow(CenteredWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("主功能菜单")
        self.init_ui()

    def init_ui(self):
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)
        for txt, slot in [("① 打卡", self.do_face_clock),
                          ("② 查看记录", self.open_record),
                          ("③ 员工管理", self.open_manage),
                          ("④ 考勤报表", self.open_report),
                          ("⑤ 退出", self.close_with_animation)]:
            btn = QPushButton(txt)
            btn.setStyleSheet(BTN_STYLE)
            btn.clicked.connect(slot)
            vbox.addWidget(btn)


    def do_face_clock(self):
        # 1. 创建 Qt 窗口
        self.cam_win = QWidget()
        self.cam_win.setWindowTitle("人脸打卡")
        self.cam_win.setFixedSize(640, 480)
        self.cam_win.center = lambda: self.cam_win.move(
            QDesktopWidget().availableGeometry().center()
            - self.cam_win.rect().center())
        self.cam_win.center()
        self.cam_win.show()

        # 2. 摄像头控件
        self.image_label = QLabel(self.cam_win)
        self.image_label.setAlignment(Qt.AlignCenter)
        self.image_label.setFixedSize(640, 480)

        # ★★★ 关键：让 QLabel 占满窗口
        layout = QVBoxLayout(self.cam_win)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.image_label)

        # 3. OpenCV 摄像头
        self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

        # 4. 定时器取帧
        self.timer = QTimer(self.cam_win)
        self.timer.timeout.connect(self.show_frame)
        self.timer.start(30)  # 30 ms 刷新

        # 5. 3 秒自动打卡
        QTimer.singleShot(3000, self.clock_shot)

    # ---------- 实时预览 ----------
    def show_frame(self):
        ret, frame = self.cap.read()
        if not ret:
            return
        rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb.shape
        img = QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888)
        self.image_label.setPixmap(QPixmap.fromImage(img))

    def do_face_clock(self):
        # 1. 创建 Qt 窗口
        self.cam_win = QWidget()
        self.cam_win.setWindowTitle("人脸打卡")
        self.cam_win.setFixedSize(640, 480)
        self.cam_win.center = lambda: self.cam_win.move(
            QDesktopWidget().availableGeometry().center()
            - self.cam_win.rect().center())
        self.cam_win.center()
        self.cam_win.show()

        # 2. 摄像头控件
        self.image_label = QLabel(self.cam_win)
        self.image_label.setAlignment(Qt.AlignCenter)
        self.image_label.setFixedSize(640, 480)

        # 让 QLabel 占满窗口
        layout = QVBoxLayout(self.cam_win)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.image_label)

        # 3. OpenCV 摄像头 - 增加错误处理
        try:
            self.cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
            # 确保摄像头正确打开
            if not self.cap.isOpened():
                raise Exception("无法打开摄像头")

            # 设置合适的分辨率
            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
            self.cap.set(cv2.CAP_PROP_BRIGHTNESS, 0.5)
            self.cap.set(cv2.CAP_PROP_CONTRAST, 0.5)
        except Exception as e:
            FuncWindow("摄像头错误", f"无法初始化摄像头: {str(e)}", 3).show_with_animation()
            self.cam_win.close()
            QTimer.singleShot(3500, self.show_with_animation)
            return

        # 4. 定时器取帧
        self.timer = QTimer(self.cam_win)
        self.timer.timeout.connect(self.show_frame)
        self.timer.start(30)  # 30 ms 刷新

        # 5. 增加倒计时提示
        self.countdown_label = QLabel("准备拍照: 3秒", self.cam_win)
        self.countdown_label.setStyleSheet("color: red; font-size: 24px; font-weight: bold;")
        self.countdown_label.setAlignment(Qt.AlignTop | Qt.AlignCenter)
        layout.addWidget(self.countdown_label)

        # 倒计时更新
        self.count = 3
        self.countdown_timer = QTimer(self.cam_win)
        self.countdown_timer.timeout.connect(self.update_countdown)
        self.countdown_timer.start(1000)

    def update_countdown(self):
        self.count -= 1
        if self.count > 0:
            self.countdown_label.setText(f"准备拍照: {self.count}秒")
        else:
            self.countdown_label.setText("正在识别...")
            self.countdown_timer.stop()
            # 倒计时结束后执行打卡
            QTimer.singleShot(500, self.clock_shot)

    def clock_shot(self):
        # 多次尝试获取清晰帧
        success, frame = False, None
        for _ in range(5):  # 尝试5次
            ret, temp_frame = self.cap.read()
            if ret:
                success = True
                frame = temp_frame
                break
            QThread.msleep(100)  # 等待100ms再试

        # 清理资源
        self.timer.stop()
        self.cap.release()
        self.cam_win.close()

        if not success or frame is None:
            FuncWindow("提示", "无法获取清晰图像，请重试", 3).show_with_animation()
            QTimer.singleShot(3500, self.show_with_animation)
            return

        # 保存调试图像
        debug_dir = "debug_images"
        os.makedirs(debug_dir, exist_ok=True)
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        cv2.imwrite(f"{debug_dir}/capture_{timestamp}.jpg", frame)

        # 优化图像预处理
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 增加对比度和亮度调整
        gray = cv2.equalizeHist(gray)
        gray = cv2.GaussianBlur(gray, (3, 3), 0)  # 轻微模糊减少噪声

        # 尝试多种级联分类器路径
        cascade_paths = [
            os.path.join(os.path.dirname(__file__), "cascades", "haarcascade_frontalface_default.xml"),
            os.path.join(os.path.dirname(__file__), "haarcascade_frontalface_default.xml"),
            r"E:\openCV\lock\cascades\haarcascade_frontalface_default.xml"
        ]

        cascade = None
        for path in cascade_paths:
            if os.path.exists(path):
                try:
                    cascade = cv2.CascadeClassifier(path)
                    break
                except:
                    continue

        if cascade is None:
            FuncWindow("错误", "无法加载人脸检测模型", 3).show_with_animation()
            QTimer.singleShot(3500, self.show_with_animation)
            return

        # 多参数组合检测策略
        detection_params = [
            # 标准参数
            {"scaleFactor": 1.1, "minNeighbors": 5, "minSize": (100, 100)},
            # 宽松参数1
            {"scaleFactor": 1.05, "minNeighbors": 3, "minSize": (80, 80)},
            # 宽松参数2
            {"scaleFactor": 1.03, "minNeighbors": 2, "minSize": (50, 50)}
        ]

        faces = None
        for params in detection_params:
            faces = cascade.detectMultiScale(
                gray,
                scaleFactor=params["scaleFactor"],
                minNeighbors=params["minNeighbors"],
                minSize=params["minSize"],
                flags=cv2.CASCADE_SCALE_IMAGE
            )
            if len(faces) > 0:
                break  # 找到人脸就停止尝试

        # 记录检测结果用于调试
        print(f"检测到的人脸数量: {len(faces) if faces is not None else 0}")

        if len(faces) == 0:
            FuncWindow("提示", "未检测到人脸，请调整光线和姿势重试", 3).show_with_animation()
            QTimer.singleShot(3500, self.show_with_animation)
            return

        # 选择最合适的人脸（面积最大的）
        largest_face = max(faces, key=lambda f: f[2] * f[3])
        x, y, w, h = largest_face

        # 保存检测到的人脸用于调试
        face_img = gray[y:y + h, x:x + w]
        cv2.imwrite(f"{debug_dir}/detected_face_{timestamp}.jpg", face_img)

        # 优化人脸预处理
        roi = cv2.resize(face_img, (200, 200), interpolation=cv2.INTER_AREA)
        roi = cv2.equalizeHist(roi)  # 再次均衡化提高识别率

        # 加载识别器 - 增加错误处理
        try:
            recognizer = cv2.face.LBPHFaceRecognizer_create()
            trainer_paths = [
                os.path.join(os.path.dirname(__file__), "face_train_data", "trainer.yml"),
                r"E:\openCV\lock\face_train_data\trainer.yml"
            ]

            trainer_loaded = False
            for path in trainer_paths:
                if os.path.exists(path):
                    try:
                        recognizer.read(path)
                        trainer_loaded = True
                        break
                    except:
                        continue

            if not trainer_loaded:
                raise Exception("无法加载训练数据文件")

            label, conf = recognizer.predict(roi)
            print(f"识别结果 - 标签: {label}, 置信度: {conf}")
        except Exception as e:
            FuncWindow("错误", f"人脸识别器初始化失败: {str(e)}", 3).show_with_animation()
            QTimer.singleShot(3500, self.show_with_animation)
            return

        # 查找员工信息
        try:
            # 1. 读取emp.json并打印全部内容（调试用）
            with open("emp.json", encoding="utf-8") as f:
                id_name = json.load(f)

            # 打印emp.json中的所有映射关系（关键调试信息）
            print("emp.json映射关系：", id_name)
            print("查找的标签（字符串形式）：", str(label))

            # 2. 检查键是否存在（处理字符串/数字键的兼容问题）
            label_str = str(label)
            if label_str not in id_name:
                # 尝试数字键兼容（如果emp.json中用了数字而非字符串）
                if label in id_name:
                    label_str = label  # 兼容数字键
                else:
                    print(f"错误：emp.json中不存在标签 {label} 的映射")
                    name = None

            # 3. 阈值判断（保持之前的逻辑）
            if conf < 50:
                threshold = 150
            elif conf < 100:
                threshold = 110
            else:
                threshold = 100

            print(f"调试：置信度={conf}，阈值={threshold}，标签={label}")
            name = id_name.get(label_str) if conf < threshold else None

            # 4. 再次检查name是否为空（即使阈值通过但映射不存在）
            if name is None :
                if conf < threshold:
                    # 置信度达标但姓名不存在（标签存在但特征不匹配）
                    FuncWindow("特征不匹配",
                               f"标签{label}存在，但人脸特征不匹配\n建议重新录入该员工", 4).show_with_animation()
                else:
                    # 置信度不达标（完全不匹配）
                    FuncWindow("未识别", f"无法识别该员工\n置信度: {conf:.1f}", 3).show_with_animation()
                QTimer.singleShot(4500, self.show_with_animation)
                return

        except FileNotFoundError:
            print("错误：emp.json文件不存在")
            name = None
        except json.JSONDecodeError:
            print("错误：emp.json格式错误（可能是语法错误）")
            name = None
        except Exception as e:
            print(f"员工数据加载错误: {e}")
            name = None

        if name:
            # 打卡成功处理
            add_lock_record(name)
            today = datetime.now().strftime("%Y-%m-%d")
            now = datetime.now().strftime("%H:%M:%S")
            try:
                with open("record.json", "r+", encoding="utf-8") as f:
                    recs = json.load(f)
                    recs.append({"date": today, "name": name, "time": now, "confidence": float(conf)})
                    f.seek(0)
                    json.dump(recs, f, ensure_ascii=False, indent=2)
                    f.truncate()
                FuncWindow("打卡成功", f"欢迎 {name}\n识别成功 (置信度: {conf:.1f})", 2).show_with_animation()
            except Exception as e:
                FuncWindow("打卡成功", f"欢迎 {name}，但记录保存失败", 3).show_with_animation()
                print(f"记录保存错误: {e}")
        else:
            FuncWindow("未识别", f"无法识别该员工\n置信度: {conf:.1f}", 3).show_with_animation()

        # 回到主菜单
        QTimer.singleShot(3000, self.show_with_animation)

    def open_record(self):
        if not ADMIN_LOGIN:
            LoginWindow().exec_()
        if ADMIN_LOGIN:
            self.close_with_animation()
            RecordWindow().show_with_animation()

    def open_manage(self):
        if not ADMIN_LOGIN:
            LoginWindow().exec_()
        if ADMIN_LOGIN:
            self.close_with_animation()
            ManageWindow().show_with_animation()

    def open_report(self):
        if not ADMIN_LOGIN:
            LoginWindow().exec_()
        if ADMIN_LOGIN:
            self.close_with_animation()
            ReportWindow().show_with_animation()

# -------------------- 记录 --------------------
class RecordWindow(CenteredWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("查看记录")
        self.init_ui()

    def init_ui(self):
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)
        for txt, slot in [("① 查看员工列表", self.show_emp_list),
                          ("② 查看打卡记录", self.show_record),
                          ("③ 返回上级菜单", self.back_main)]:
            btn = QPushButton(txt)
            btn.setStyleSheet(BTN_STYLE)
            btn.clicked.connect(slot)
            vbox.addWidget(btn)

    def show_emp_list(self):
        self.close_with_animation()
        emps = get_employee_report().strip()
        FuncWindow("员工列表", emps if emps else "None", 9).show_with_animation()
        QTimer.singleShot(4000, self.show_with_animation)

    def show_record(self):
        self.close_with_animation()
        recs = get_record_all().strip()
        FuncWindow("打卡记录", recs if recs else "None", 9).show_with_animation()
        QTimer.singleShot(4000, self.show_with_animation)

    def back_main(self):
        self.close_with_animation()
        MainMenuWindow().show_with_animation()

# -------------------- 员工管理 --------------------
class ManageWindow(CenteredWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("员工管理")
        self.init_ui()

    def init_ui(self):
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)
        for txt, slot in [("① 录入新员工", self.add_emp_gui),
                          ("② 删除员工", self.del_emp_gui),
                          ("③ 返回上级菜单", self.back_main)]:
            btn = QPushButton(txt)
            btn.setStyleSheet(BTN_STYLE)
            btn.clicked.connect(slot)
            vbox.addWidget(btn)

    def add_emp_gui(self):
        self.close_with_animation()
        w = InputWindow("录入新员工", "请输入新员工姓名", placeholder="输入员工姓名")
        w.ok.connect(self._do_add)
        w.show_with_animation()



    def _do_add(self, name):
        name = name.strip()
        if not name:
            return

        # 生成正确的连续标签
        try:
            with open("emp.json", encoding="utf-8") as f:
                emp_dict = json.load(f)
        except:
            emp_dict = {}

        valid_keys = [int(k) for k in emp_dict.keys() if k.isdigit()]
        new_code = str(max(valid_keys, default=0) + 1)
        print(f"新增员工标签：{new_code}，姓名：{name}")

        # 先写入emp.json，确保训练时标签已存在
        emp_dict[new_code] = name
        with open("emp.json", "w", encoding="utf-8") as f:
            json.dump(emp_dict, f, ensure_ascii=False, indent=2)

        # 优化人脸训练流程：强制重新生成训练数据，避免旧特征干扰
        def _shot():
            try:
                # 确保训练目录存在
                train_dir = os.path.join(os.path.dirname(__file__), "face_train_data")
                os.makedirs(train_dir, exist_ok=True)

                # 1. 拍摄新员工人脸（拍摄5张不同角度）
                faces = []
                ids = []
                cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
                cascade = cv2.CascadeClassifier(r"E:\openCV\lock\cascades\haarcascade_frontalface_default.xml")

                print(f"开始拍摄 {name}（标签{new_code}）的人脸样本...")
                count = 0
                while count < 5:  # 拍摄5张样本
                    ret, frame = cap.read()
                    if not ret:
                        continue
                    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                    face_rects = cascade.detectMultiScale(gray, 1.3, 5)

                    for (x, y, w, h) in face_rects:
                        faces.append(gray[y:y + h, x:x + w])
                        ids.append(int(new_code))  # 明确关联新标签
                        count += 1
                        cv2.imshow(f"拍摄中（{count}/5）", frame)
                        cv2.waitKey(1000)  # 间隔1秒拍摄

                cap.release()
                cv2.destroyAllWindows()

                # 2. 重新训练模型（包含所有员工，确保新标签生效）
                recognizer = cv2.face.LBPHFaceRecognizer_create()
                recognizer.train(faces, np.array(ids))

                # 3. 保存新模型，覆盖旧模型
                trainer_path = os.path.join(train_dir, "trainer.yml")
                recognizer.write(trainer_path)
                print(f"模型训练完成，已保存到 {trainer_path}")

                w.label.setText(f"录入成功：{name}（标签{new_code}）")
                QTimer.singleShot(2000, lambda: (
                    w.close_with_animation(),
                    self.show_with_animation()
                ))
            except Exception as e:
                w.label.setText(f"录入失败：{str(e)}")
                print(f"训练错误：{e}")
                QTimer.singleShot(2000, lambda: (
                    w.close_with_animation(),
                    self.show_with_animation()
                ))
            # QTimer.singleShot(2000, w.close_with_animation)
            # QTimer.singleShot(2100, self.show_with_animation)

        # 显示拍摄提示
        w = FuncWindow("拍摄人脸", "请正对摄像头，会自动拍摄5张样本", 0)
        w.show_with_animation()
        QTimer.singleShot(1000, _shot)  # 延迟1秒开始拍摄





    def del_emp_gui(self):
        self.close_with_animation()
        # 1. 重新加载最新员工列表
        try:
            with open("emp.json", encoding="utf-8") as f:
                emp = json.load(f)
            lines = [f"{k}: {v}" for k, v in emp.items()]
            show_text = "\n".join(lines) if lines else "None"
        except:
            show_text = "None"

        FuncWindow("删除员工", show_text, 0).show_with_animation()

        # 2. 弹出输入框
        w = InputWindow("删除员工", "请输入要删除的员工编号（输入 0 取消）", placeholder="输入编号(如1001)")
        w.ok.connect(self._do_del)
        w.show_with_animation()

    def _do_del(self, sid):
        sid = sid.strip()
        if sid == "0":
            return
        try:
            with open("emp.json", encoding="utf-8") as f:
                emp = json.load(f)
        except:
            emp = {}

        if sid in emp:  # 直接在新文件里判断
            # 2.1 先删人脸文件（原逻辑保留）
            if check_id(int(sid)):
                remove_employee(int(sid))

            # 2.2 再删姓名映射
            emp.pop(sid)
            with open("emp.json", "w", encoding="utf-8") as f:
                json.dump(emp, f, ensure_ascii=False, indent=2)

            FuncWindow("删除成功", f"{sid} 号员工已删除", 2).show_with_animation()
        else:
            FuncWindow("删除失败", "编号不存在", 2).show_with_animation()

        QTimer.singleShot(2000, self.show_with_animation)

    def back_main(self):
        self.close_with_animation()
        MainMenuWindow().show_with_animation()


class DayReportWindow(CenteredWidget):
    def __init__(self, date_str, lines):
        super().__init__()
        self.setWindowTitle("日报详情")
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)

        text = "\n".join(lines) if lines else "None"
        label = QLabel(text)
        label.setFont(QFont("Consolas", 16))
        label.setStyleSheet("color:#00FFFF;")
        label.setAlignment(Qt.AlignCenter)
        vbox.addWidget(label)

        back = QPushButton("返回")
        back.setStyleSheet(BTN_STYLE)
        back.clicked.connect(self.close_with_animation)
        back.clicked.connect(lambda: RecordWindow().show_with_animation())
        vbox.addWidget(back)

        # 5 秒后自动回到日报菜单
        QTimer.singleShot(5000, self.close_with_animation)
        QTimer.singleShot(5100, lambda: RecordWindow().show_with_animation())


# ------------ 兜底：export_month_report_csv ------------
import json, csv, os
from datetime import datetime, timedelta
from PyQt5.QtCore import QThread, pyqtSignal
def load_work_time():
    """返回(上班,下班) 默认 08:30:00 / 17:30:00"""
    import os, json
    file = "work_time.json"
    if not os.path.exists(file):
        return "08:30:00", "17:30:00"
    with open(file, encoding="utf-8") as f:
        data = json.load(f)
    return data.get("work", "08:30:00"), data.get("close", "17:30:00")

def export_month_report_csv(month: str) -> str:
    """生成 CSV 字符串：姓名,日期,状态"""
    work, close = load_work_time()
    w_dt = datetime.strptime(work,  "%H:%M:%S")
    c_dt = datetime.strptime(close, "%H:%M:%S")

    # 员工
    try:
        with open("emp.json", encoding="utf-8") as f:
            emps = list(json.load(f).values())
    except:
        emps = []

    # 当月记录
    try:
        with open("record.json", encoding="utf-8") as f:
            recs = json.load(f)
    except:
        recs = []
    month_recs = [r for r in recs if r.get("date", "").startswith(month)]

    from collections import defaultdict
    day_map = defaultdict(list)
    for r in month_recs:
        day_map[r["date"]].append(r)

    rows = ["姓名,日期,状态"]
    for day in sorted(day_map):
        arrived = {rec["name"]: rec["time"] for rec in day_map[day]}
        for emp in emps:
            if emp not in arrived:
                rows.append(f"{emp},{day},缺席")
            else:
                t = datetime.strptime(arrived[emp], "%H:%M:%S")
                status = "迟到" if t > w_dt else ("早退" if t < c_dt else "正常")
                rows.append(f"{emp},{day},{status}")
    return "\n".join(rows) + "\n"


# -------------------- 考勤报表 --------------------
class ReportWindow(CenteredWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("考勤报表")
        self._day_worker = None  # 新增
        self._month_worker = None
        self.init_ui()

    # ================= 内部 UI =================
    def init_ui(self):
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)
        for txt, slot in [("① 日报", self.day_report),
                          ("② 月报", self.month_report),
                          ("③ 报表设置", self.set_report),
                          ("④ 返回上级菜单", self.back_main)]:
            btn = QPushButton(txt)
            btn.setStyleSheet(BTN_STYLE)
            btn.clicked.connect(slot)
            vbox.addWidget(btn)

    # ================= 日报 =================
    def day_report(self):
        w = InputWindow("日报", "请输入查询日期，格式(2008-08-08)\n输入 0 查询今天",
                        placeholder="格式：2008-08-08 或 0")
        w.ok.connect(self._do_day)
        w.show_with_animation()

    # ---------- 线程 + 结果弹窗 ----------
    class _DayWorker(QThread):
        done = pyqtSignal(str)

        def __init__(self, date_str):
            super().__init__()
            self.date_str = date_str

        def run(self):
            from datetime import datetime
            work, close = load_work_time()
            w_dt  = datetime.strptime(work,  "%H:%M:%S")
            c_dt  = datetime.strptime(close, "%H:%M:%S")

            try:        # 员工
                with open("emp.json", encoding="utf-8") as f:
                    emps = list(json.load(f).keys())
            except:
                emps = []

            try:        # 当日记录
                with open("record.json", encoding="utf-8") as f:
                    recs = json.load(f)
            except:
                recs = []
            today = [r for r in recs if r.get("date") == self.date_str]

            arrived = {r["name"] for r in today}
            absent  = [n for n in emps if n not in arrived]

            late, early = [], []
            for r in today:
                t = datetime.strptime(r["time"], "%H:%M:%S")
                if t > w_dt:
                    late.append(r["name"])
                if t < c_dt:
                    early.append(r["name"])
            late  = list(set(late))
            early = list(set(early))

            txt = f"""应到人数：{len(emps)}
缺席人数：{len(absent)}
缺席名单：{','.join(absent) or '无'}

迟到人数：{len(late)}
迟到名单：{','.join(late) or '无'}

早退人数：{len(early)}
早退名单：{','.join(early) or '无'}"""
            self.done.emit(txt)

    class _DayResultWindow(CenteredWidget):
        def __init__(self, date_str, text):
            super().__init__()
            self.setWindowTitle(f"日报详情 —— {date_str}")
            self.setFixedSize(800, 500)
            vbox = QVBoxLayout(self)
            vbox.setAlignment(Qt.AlignCenter)
            label = QLabel(text)
            label.setFont(QFont("Microsoft YaHei", 18))
            label.setStyleSheet("color:#00FFFF;")
            label.setAlignment(Qt.AlignCenter)
            vbox.addWidget(label)
            back = QPushButton("返回")
            back.setStyleSheet(BTN_STYLE)
            back.clicked.connect(self.close_with_animation)
            back.clicked.connect(lambda: ReportWindow().show_with_animation())
            vbox.addWidget(back)

    def _do_day(self, d):
        d = d.strip()
        if d == '0':
            d = datetime.now().strftime("%Y-%m-%d")
        if not valid_date(d):
            FuncWindow("错误", "日期格式错误", 3).show_with_animation()
            QTimer.singleShot(3000, self.show_with_animation)
            return

        loading = FuncWindow("日报", "正在查询，请稍候…", 0)
        loading.show_with_animation()

        self._day_worker = self._DayWorker(d)
        self._day_worker.done.connect(lambda txt: (
            loading.close_with_animation(),
            self._DayResultWindow(d, txt).show_with_animation()
        ))
        self._day_worker.start()

    # ================= 月报 =================
    def month_report(self):
        w = InputWindow("月报", "请输入查询月份，格式(2008-08)\n输入 0 查询上个月",
                        placeholder="格式：2008-08 或 0")
        w.ok.connect(self._do_month)
        w.show_with_animation()

    class _MonthWorker(QThread):
        done = pyqtSignal(str)

        def __init__(self, month):
            super().__init__()
            self.month = month

        def run(self):
            csv_path = os.path.join(os.getcwd(), f"月报_{self.month}.csv")
            data = export_month_report_csv(self.month)
            with open(csv_path, "w", encoding="utf-8-sig", newline='') as f:
                f.write(data)
            self.done.emit(csv_path)

    def _do_month(self, m):
        m = m.strip()
        if m == '0':
            m = (datetime.now().replace(day=1) - timedelta(days=1)).strftime("%Y-%m")
        if not valid_year_month(m):
            FuncWindow("错误", "月份格式错误", 3).show_with_animation()
            QTimer.singleShot(3000, self.show_with_animation)
            return

        loading = FuncWindow("月报", "正在生成 CSV，请稍候…", 0)
        loading.show_with_animation()

        self._month_worker = self._MonthWorker(m)
        self._month_worker.done.connect(lambda path: (
            loading.close_with_animation(),
            subprocess.Popen(['start', '', path], shell=True)
        ))
        self._month_worker.start()

    # ================= 作息时间设置 =================
    def set_report(self):
        w = InputWindow("作息时间设置", "请输入上班时间，格式(08:00:00)",
                        placeholder="上班时间格式：08:00:00")
        w.ok.connect(self._set_work)
        w.show_with_animation()

    def _set_work(self, work):
        if not valid_time(work):
            FuncWindow("错误", "上班时间格式错误", 2).show_with_animation()
            QTimer.singleShot(2000, self.show_with_animation)
            return
        w2 = InputWindow("作息时间设置", "请输入下班时间，格式(23:59:59)",
                         placeholder="下班时间格式：23:59:59")
        w2.ok.connect(lambda close: self._save_time(work, close))
        w2.show_with_animation()

    def _save_time(self, work, close):
        if valid_time(close):
            save_work_time(work, close)
            FuncWindow("设置完成", f"上班:{work} 下班:{close}", 2).show_with_animation()
        else:
            FuncWindow("错误", "下班时间格式错误", 2).show_with_animation()
        QTimer.singleShot(2000, self.show_with_animation)

    # ================= 返回主菜单 =================
    def back_main(self):
        self.close_with_animation()
        MainMenuWindow().show_with_animation()

class DayReportDisplay(CenteredWidget):
    def __init__(self, date_str, content):
        super().__init__()
        self.setWindowTitle("日报详情")
        vbox = QVBoxLayout(self)
        vbox.setAlignment(Qt.AlignCenter)

        label = QLabel(content if content.strip() else "None")
        label.setFont(QFont("Consolas", 18))
        label.setStyleSheet("color:#00FFFF;")
        label.setAlignment(Qt.AlignCenter)
        vbox.addWidget(label)

        back = QPushButton("返回")
        back.setStyleSheet(BTN_STYLE)
        back.clicked.connect(self.close_with_animation)
        vbox.addWidget(back)

        # 5 秒后自动回到日报
        QTimer.singleShot(5000, self.close_with_animation)
        QTimer.singleShot(5100, lambda: RecordWindow().show_with_animation())

# -------------------- 启动 --------------------
if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setAttribute(Qt.AA_UseHighDpiPixmaps)
    app.setAttribute(Qt.AA_EnableHighDpiScaling)

    login_win = LoginWindow()
    login_win.show_with_animation()
    login_win.login_success.connect(lambda: MainMenuWindow().show_with_animation())

    sys.exit(app.exec_())
